function mlcompCompiler(fitFn, predictFn, outputDir, fitOpts, predictOpts)
% Encapsulate fit and predict functions into an mlcomp compatible run
% script, which includes all dependent files. You must ensure that all
% functions are terminated with the syntactically optional 'end' keyword. 
% This function does not support mex files. The generated file will only
% run in octave, not Matlab. 
%
%% See Also:
% mlcompWriteData  - Converts matlab data to the mlcomp format
% mlcompReadData   - Converts mlcomp data to matlab format
%% Example:
% mlcompCompiler('linregFitSimple', 'linregPredict',...
%                'C:\users\matt\Desktop\mlcomp', {0.1});
%
%% INPUTS:
%
% fitFn         - The name of the fit function, i.e. 'linregFit'
%                 This function must have this interface:
%                 model = fitFn(X, y, fitOpts{:})
% 
% predictFn     - The name of the predict function, i.e. 'linregPredict'
%                 This function must have this interface:
%                 yhat = predictFn(model, X, predictOpts{:})
%
% outputDir     - a file called "run" is created and stored in this
%                 directory. (Default: current directory)
%
% fitOpts       - a cell array of optional arguments for the fit function
%                 Do not include data - this is handled by mlcomp
%                 separately.
%
% predictOpts   - a cell array of optional arguments for the predict
%                 function. Do not include data - this is handled by mlcomp
%                 separately.
%% Local Testing
% The mlcomp system will execute the run script with the equivalent of
% the following commands:
% octave -qf run learn path/to/training/Data
% octave -qf run predict path/to/Test/Data /path/to/output/predictions
%
% where data is in the mlcomp format. You should test your script locally
% using these commands before uploading to mlcomp. 
%%

% This file is from pmtk3.googlecode.com

if nargin < 3, outputDir   = pwd(); end
if nargin < 4, fitOpts     = {};    end
if nargin < 5, predictOpts = {};    end 
if endswith(fitFn,     '.m'), fitFn     = fitFn    (1:end-2); end
if endswith(predictFn, '.m'), predictFn = predictFn(1:end-2); end
getT = @(s)removeComments(getText(s));
%% Get Core Functions
fitText     = getT(fitFn);
predictText = getT(predictFn);
%% Find Dependencies
fitDependencies      = depfunFast(fitFn, true);
predictDependencies  = depfunFast(predictFn, true);
dependencies         = union(fitDependencies, predictDependencies);
dependencies         = union(dependencies, {'getText.m', 'mlcompReadData.m'});
[mexf, dependencies] = partitionCell(dependencies, @(s)isSubstring('.mex', s));
for i=1:numel(mexf)
    fname = [strtok(mexf{i}, '.'), '.m'];
    if exist(fname, 'file')
        dependencies = union(dependencies, {fname});
    else
       warning('mlcomp:missingMexFile',...
       'No corresponding m-file could be found for the mex file %s.', mexf{i});
    end 
end
%% Check Dependencies
allfiles = [dependencies, fitFn, predictFn];
ndx = cellfun(@isEndKeywordMissing, allfiles);
if any(ndx)
   warning('mlcomp:missingEndKeyword',...
 'The following functions may be missing a terminating end statement %s.', catString(allfiles(ndx), ', '));
end
%% Serialize Optionalal Inputs
fitFnOptionText     = serialize(fitOpts);
predictFnOptionText = serialize(predictOpts);
%% Create Octave Script Text
text = {
    '#!/usr/bin/octave -qf'
    '% This file was autogenerated by mlcompCompiler.m and will'
    '% only run in Octave.'
    ''
    'args = argv(); % remainder of script begins after function defs.'
    ''
    '%%%%%%%%%%%% FIT FUNCTION %%%%%%%%%%%%'
    };
text = [text
    fitText
    '%%%%%%%%%%%% PREDICT FUNCTION %%%%%%%%%%%%'
    predictText
    '%%%%%%%%%%%% DEPENDENCIES %%%%%%%%%%%%'
    ];
for i=1:numel(dependencies)
    text = [text; getT(dependencies{i})]; %#ok
end
text = [text
    '%%%%%%%%%%%% START SCRIPT %%%%%%%%%%%%'
    'switch(args{1})'
    '    case ''learn'''
    '        [X, y] = mlcompReadData(args{2});'
    sprintf('        fitArgs = %s', fitFnOptionText);
    sprintf('        model = %s(X, y, fitArgs{:});', fitFn);
    '        save(''model'', ''model'');'
    '    case ''predict'''
    '        S = load(''model'');'
    '        model = S.model;'
    '        [X, y] = mlcompReadData(args{2});'
    sprintf('        predictArgs = %s', predictFnOptionText);
    sprintf('        yhat = %s(model, X, predictArgs{:});', predictFn);
    '        fid = fopen(args{3}, ''w'');'
    '        for i=1:numel(yhat)'
    '            fprintf(fid, ''%f\n'', yhat(i));'
    '        end'
    '        fclose(fid);'
    '    otherwise'
    '        fprintf(''%s is not a supported option'', args{1});'
    'end'
    ];
%% Write to File
fname = fullfile(outputDir, 'run')
writeText(text, fname);
end
